﻿namespace UHIDLInk
{
    /// <summary>
    /// CH9329 芯片串口通信协议V1.2
    /// </summary>
    public class TransferProtocol
    {
        /// <summary>
        /// FrameCMD的可选值及格式说明
        /// </summary>
        public enum Commands
        {
            /// <summary>
            /// 获取芯片版本等信息 通过该命令向芯片获取版本号、USB 枚举状态、键盘大小写指示灯状态等信息
            /// </summary>
            CMD_GET_INFO = 0x01,
            /* CMD_GET_INFO 返回包格式：
             * 帧头 地址码 命令码 后续数据长度 后续数据 累加和
             * HEAD ADDR CMD LEN DATA SUM
             * 0x57、0xAB 0x00 0x81 0x08 8 个字节数据 0x??
             * 返回的8 个字节后续数据依次为：
             * (1)、1个字节芯片版本号：如0x30 表示V1.0，如0x31表示V1.1；
             * (2)、1 个字节USB 枚举状态:
             * 0x00 表示USB端未连接计算机或未识别；
             * 0x01 表示USB端已连接计算机并识别成功；
             * (3)、1个字节当前键盘大小指示灯状态信息；
             * 位0：键盘NUM LOCK 指示灯状态，0：熄灭；1：点亮；
             * 位1：键盘CAPS LOCK 指示灯状态，0：熄灭；1：点亮；
             * 位2：键盘SCROLL LOCK指示灯状态，0：熄灭；1：点亮；
             * 位7---3：无效；
             * (4)、5个字节保留；
             */
            /// <summary>
            /// 通过该命令向芯片发送普通键盘数据包，模拟普通按键按下或释放动作。
            /// 支持全键盘、组合键操作，可支持8+6个无冲突按键，其中8为8个控制键(左Ctrl、右Ctrl、左Shift、右Shift、左Windows、右Windows、左Alt 和右Alt)，6 为6 个控制键之外的普通按键。
            /// </summary>
            CMD_SEND_KB_GENERAL_DATA = 0x02,
            /*该命令带8个字节的后续数据，后续数据为USB键盘普通按键的键值。
             * 依次为：
             * (1)、第1个字节：1 个字节的控制键，每个位表示1 个按键，具体如下：
             * BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0
             * 右Windows键 右Alt键 右Shift键 右Ctrl键 左Windows键 左Alt键 左Shift键 左Ctrl键
             * (2)、第2 个字节：1个字节0x00，该字节必须为0x00；
             * (3)、第3-8个字节：6 个字节普通按键值，最多可以表示6 个按键按下，如果无按键按下则填写0x00；
             * 具体键盘普通按键及对应的键码见附录1-“CH9329键码表”。
             * 
             * 芯片到外围串口设备
             * 
             * 返回的1个字节后续数据为：当前命令执行状态。
             * 帧头 地址码 命令码 后续数据长度 后续数据 累加和
             * HEAD ADDR CMD LEN DATA SUM
             * 0x57、0xAB 0x00 0x82 1 1 个字节数据 0x??
             */
            /// <summary>
            /// 通过该命令向芯片发送多媒体键盘数据包，模拟多媒体按键按下或释放动作。
            /// </summary>
            CMD_SEND_KB_MEDIA_DATA = 0x03,
            /* 帧头 地址码 命令码 后续数据长度 后续数据 累加和
             * HEAD ADDR CMD LEN DATA SUM
             * 0x57、0xAB 0x00 0x03 2 2 个字节数据 0x??
             * 该命令带2个字节的后续数据，后续数据为USB键盘多媒体按键的键值。
             * 具体键盘普通按键及对应的键码见附录1-“CH9329键码表”。
             * 对于其它多媒体键，占4 个字节，第1 个字节为REPORT ID，固定为0x02，第2 个字节至第4 个字节为多媒体键值。
             * 字节号 Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
             * 1 00000010b
             * 2 Eject,CD Stop,Prev.Track,NextTrack,Play/Pause,Mute,Volume-,Volume+
             * 3 Refresh,WWW Stop,WWW Forward,WWW Back,WWW Home,WWW Favorites,WWW Search,E-Mail
             * 4 Rewind,Record,Minimize,My Computer,Screen Save,Calculator,Explorer,Media
             * 1:键按下 0:键释放
             */
            /// <summary>
            /// 通过该命令向芯片发送绝对鼠标数据包，模拟绝对鼠标相关动作(包括左中右键按下与释放、滚轮上下滚动、上下左右移动)。
            /// </summary>
            CMD_SEND_MS_ABS_DATA = 0x04,
            /* 帧头 地址码 命令码 后续数据长度 后续数据 累加和
             * HEAD ADDR CMD LEN DATA SUM 
             * 0x57、0xAB 0x00 0x04 7 7 个字节数据 0x??
             * 该命令带7个字节后续数据，7 个字节后续数据为USB 绝对鼠标的数据包，依次为：
             * (1)、第1 个字节：必须为0x02；
             * (2)、第2 个字节：1个字节的鼠标按键值，最低3 位每位表示1 个按键，具体如下：
             * BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0
             * 0 0 0 0 0 中键 右键 左键
             * BIT2---BIT0：为1 表示该键按下，为0表示该键释放或未按下。
             * (2)、第3-4 个字节：2 个字节X 轴坐标值，低字节在前，高字节在后；
             * (3)、第5-6个字节：2 个字节Y 轴坐标值，低字节在前，高字节在后；
             * (4)、第7 个字节： 1个字节滚轮滚动齿数，
             * 如果为0，则表示滚动无动作；
             * 0x01---0x7F，表示向上滚动，单位：齿数；
             * 0x81---0xFF，表示向下滚动，单位：齿数；
             * 
             * 芯片à外围串口设备：
             * 帧头 地址码 命令码 后续数据长度 后续数据 累加和
             * HEAD ADDR CMD LEN DATA SUM
             * 0x57、0xAB 0x00 0x84 1 1 个字节数据 0x??
             * 返回的1 个字节后续数据为：当前命令执行状态。
             * 
             * 注意：芯片默认模拟的绝对鼠标分辨率为4096 * 4096，外围串口设备下传XY 绝对值时，需要先根据自身屏幕分辨率进行计算，再下传计算后的值。
             * 比如当前屏幕分辨率为：X_MAX(1280) * Y_MAX(768)，则需要移动到点(100，120)，需要进行如下计算：
             * 
             * X_Cur = ( 4096 * 100 ) / X_MAX；Y_Cur = ( 4096 * 120 ) / Y_MAX；
             * 以下举例进行说明：
             * 例如1：模拟先按下鼠标“左”键，再释放鼠标“左”键，则需要发送2个命令包为：
             * (1)、按下鼠标“左”键: 0x57、0xAB、0x00、0x04、0x07、0x02、0x01、0x00、0x00、0x00、0x00、0x00、0x10。
             * (2)、释放鼠标“左”键: 0x57、0xAB、0x00、0x04、0x07、0x02、0x00、0x00、0x00、0x00、0x00、0x00、0x0F。
             * 例如2：假设屏幕分辨率为：1280*768，控制鼠标先移动到(100，100)位置，再移动到(968,500)位置，则需要发送2个命令包为：
             * (1)、移动到(100，100)位置:
             * 计算位置X1 = ( 100 * 4096 ) / 1280 = 320 = 0x140
             * 计算位置Y1 = ( 100 * 4096 ) / 768 = 533 = 0x215
             * 发送命令包为：0x57、0xAB、0x00、0x04、0x07、0x02、0x00、0x40、0x01、0x15、0x02、0x00、0x67。
             * (2)、移动到(968,500)位置:
             * 计算位置X1 = ( 968 * 4096 ) / 1280 = 3097 = 0xC19
             * 计算位置Y1 = ( 500 * 4096 ) / 768 = 2667 = 0xA6B
             * 发送命令包为：0x57、0xAB、0x00、0x04、0x07、0x02、0x00、0x19、0x0C、0x6B、0x0A、0x00、0xA9。
             * 
             * 
             * 
             * 
             */
            /// <summary>
            /// 通过该命令向芯片发送相对鼠标数据包，模拟相对鼠标相关动作(包括左中右键按下与释放、滚轮上下滚动、上下左右移动)。
            /// </summary>
            CMD_SEND_MS_REL_DATA = 0x05,
            /* 帧头 地址码 命令码 后续数据长度 后续数据 累加和
             * HEAD ADDR CMD LEN DATA SUM
             * 0x57、0xAB 0x00 0x05 0x05个字节数据 0x??
             * 
             * 该命令带5个字节后续数据，后续数据为USB 相对鼠标的数据包，依次为：
             * (1)、第1 个字节：必须为0x01；
             * (2)、第2 个字节：1个字节的鼠标按键值，最低3 位每位表示1 个按键，具体如下：
             * BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0
             * 0 0 0 0 0 中键 右键 左键
             * BIT2---BIT0：为1 表示该键按下，为0表示该键释放或未按下。
             * (3)、第3 个字节：1个字节X 方向(横坐标，左右方向)移动距离；
             * A、不动：字节3 = 0x00，则表示在X轴方向不移动；
             * B、向右移动：0x01 <= 字节3 <= 0x7F； 移动像素点 = 字节3；
             * C、向左移动：0x80 <= 字节3 <= 0xFF； 移动像素点 = 0x100 - 字节3；
             * (4)、第4 个字节：1个字节Y方向(纵坐标，上下方向)移动距离；
             * A、不动：字节4 = 0x00，则表示在Y轴方向不移动；
             * B、向下移动：0x01 <= 字节4 <= 0x7F； 移动像素点 = 字节4；
             * C、向上移动：0x80 <= 字节4 <= 0xFF； 移动像素点 = 0x100 - 字节4；
             * (5)、第5 个字节： 1个字节滚轮滚动齿数，
             * 0x01---0x7F，表示屏幕向上滚动，单位：齿数；
             * 0x81---0xFF，表示屏幕向下滚动，单位：齿数；
             * 向下滚动移动的距离计算方法：
             * 例如该字节为0x81，实际移动距离=0x100-0x81=127个像素；
             * 例如该字节为0xFF，实际移动距离=0x100-0xFF=1 个像素。
             * 
             * 芯片à外围串口设备：
             * 帧头 地址码 命令码 后续数据长度 后续数据 累加和
             * HEAD ADDR CMD LEN DATA SUM
             * 0x57、0xAB 0x00 0x85 1 1 个字节数据 0x??
             * 返回的1 个字节后续数据为：当前命令执行状态。
             * 以下举例进行说明：
             * 例如1：模拟先按下鼠标“左”键，再释放鼠标“左”键，则需要发送2个命令包为：
             * (1)、按下鼠标“左”键: 0x57、0xAB、0x00、0x05、0x05、0x01、0x01、0x00、0x00、0x00、0x0E。
             * (2)、释放鼠标“左”键: 0x57、0xAB、0x00、0x05、0x05、0x01、0x00、0x00、0x00、0x00、0x0D。
             * 例如2：控制鼠标先向左移动3 个像素点，再向下移动5 个像素点，则需要发送2 个命令包为：
             * (1)、先向左移动3个像素点:0x57、0xAB、0x00、0x05、0x05、0x01、0x00、0xFD、0x00、0x00、0x0A。
             * (2)、向下移动5个像素点: 0x57、0xAB、0x00、0x05、0x05、0x01、0x00、0x00、0x05、0x00、0x12。
             */
            /// <summary>
            /// 通过该命令向芯片发送自定义HID 类设备数据包。
            /// </summary>
            CMD_SEND_MY_HID_DATA = 0x06,
            /*帧头 地址码 命令码 后续数据长度 后续数据 累加和
             * HEAD ADDR CMD LEN DATA SUM
             * 0x57、0xAB 0x00 0x06 N N 个字节数据 0x??
             * 
             * 该命令带N 个字节的后续数据，后续数据就是希望通过USB 上传的HID 数据包，N 有效范围为：0-64；
             * 芯片à外围串口设备：
             * 帧头 地址码 命令码 后续数据长度 后续数据 累加和
             * HEAD ADDR CMD LEN DATA SUM
             * 0x57、0xAB 0x00 0x86 1 1 个字节数据 0x??
             */
            /// <summary>
            /// 通过该命令从芯片读取自定义HID类设备数据包。PC机向芯片下传1 包自定义HID数据包后，由芯片串口自动打包发送给外围串口设备。
            /// </summary>
            CMD_READ_MY_HID_DATA = 0x87,
            /* 帧头 地址码 命令码 后续数据长度 后续数据 累加和
             * HEAD ADDR CMD LEN DATA SUM
             * 0x57、0xAB 0x00 0x87 N N 个字节数据 0x??
             * 该命令带N 个字节的后续数据，后续数据就是USB 下传的HID 数据包，N 有效范围为：* 0-64；
             * 注意：该命令由芯片主动发送给外围串口设备，不需要外围串口设备进行应答。
             */
            /// <summary>
            /// 通过该命令向芯片获取当前参数配置信息,具体参数见下面返回数据说明。
            /// </summary>
            CMD_GET_PARA_CFG = 0x08,
            /* 帧头 地址码 命令码 后续数据长度 后续数据 累加和
             * HEAD ADDR CMD LEN DATA SUM
             * 0x57、0xAB 0x00 0x08 0 无 0x??
             * 
             * 芯片à外围串口设备：
             * 帧头 地址码 命令码 后续数据长度 后续数据 累加和
             * HEAD ADDR CMD LEN DATA SUM
             * 0x57、0xAB 0x00 0x88 50 50 个字节数据 0x??
             * 
             * 返回的50 个字节后续数据为：
             * (1)、1个字节芯片工作模式：有效值为0x00-0x03、0x80---0x83，默认为0x80；
             * 0x00：软件设置的工作模式0，标准USB键盘(普通+多媒体)+USB 鼠标(绝对鼠标+相对鼠标)；
             * 0x01：软件设置的工作模式1，标准USB 键盘(普通)；
             * 0x02：软件设置的工作模式2，标准USB 鼠标(绝对鼠标+相对鼠标)；
             * 0x03：软件设置的工作模式3，标准USB 自定义HID 类设备；
             * 0x80：硬件引脚设置的工作模式0，标准USB键盘(普通+多媒体)+USB鼠标(绝对鼠标+相对鼠标)；当前MODE1 引脚为高电平，MODE0 引脚为高电平；
             * 0x81：硬件引脚设置的工作模式1，标准USB 键盘(普通)；当前MODE1引脚为高电平，MODE0 引脚为低电平；
             * 0x82：硬件引脚设置的工作模式2，标准USB鼠标(绝对鼠标+相对鼠标)；当前MODE1引脚为低电平，MODE0引脚为高电平；
             * 0x83：硬件引脚设置的工作模式3，标准USB 自定义HID 类设备；当前MODE1引脚为低电平，MODE0 引脚为低电平；
             * (2)、1个字节芯片串口通信模式，有效值为0x00-0x02、0x80---0x82，默认为0x80；
             * 0x00：软件设置的串口通信模式0，协议传输模式；
             * 0x01：软件设置的串口通信模式1，ASCII模式；
             * 0x02：软件设置的串口通信模式2，透传模式；
             * 0x80：硬件引脚设置的串口通信模式0，协议传输模式；当前CFG1引脚为高电平，CFG0 引脚为高电平；
             * 0x81：硬件引脚设置的串口通信模式1，ASCII 模式；当前CFG1引脚为高电平，CFG0引脚为低电平；
             * 0x82：硬件引脚设置的串口通信模式2，透传模式；当前CFG1引脚为低电平，CFG0引脚为高电平
             * (3)、1个字节芯片串口通信地址，有效范围为0x00--0xFF，默认为0x00；
             * (4)、4 个字节芯片串口通信波特率，高字节在前，默认为0x00002580，即波特率为9600bps；
             * (5)、2个字节保留；
             * (6)、2个字节芯片串口通信包间隔，有效范围为0x0000--0xFFFF，默认为3，单位为mS，即芯片如果超过3mS未接收到下一个字节则表示本包结束；
             * (7)、4个字节芯片USB的VID和PID，默认芯片VID为0x1A86，PID为0xE129，不同工作模式下，PID 有所区别；
             * (8)、2 个字节芯片USB 键盘上传时间间隔(仅ASCII 模式有效)，有效范围为0x0000--0xFFFF，默认为0，单位为mS，即芯片上传完前1 包数据之后立马上传下一包数据；
             * (9)、2 个字节芯片USB 键盘释放延时时间(仅ASCII 模式有效)，有效范围为0x0000--0xFFFF，默认为1，单位为mS，即芯片上传完按键按下数据包之后1mS后上传按键释放数据包；
             * (10)、1个字节芯片USB键盘自动回车标志(仅ASCII模式有效)，有效范围为0x00--0x01，0x00 表示不自动进行回车，0x01 表示本包结束后自动进行回车；
             * (11)、8 个字节芯片USB键盘回车符(仅ASCII模式有效)，4 个字节一组，共2 组，即可以设置2 种不同的回车符，默认遇到ASCII值为0x0D时进行回车；
             * (12)、8 个字节芯片USB键盘过滤开始、结束字符串，前4 个字节为过滤开始字符，后4个字节为过滤结束字符；
             * (13)、1 个字节芯片USB 字符串使能标志，
             * 位7：为0 表示禁止；为1 表示使能自定义字符串描述符；
             * 位6-3：保留；位2：为0 表示禁止；
             * 位2: 表示使能自定义厂商字符串描述符；
             * 位1：为0 表示禁止；为1 表示使能自定义产品字符串描述符；
             * 位0：为0 表示禁止；为1 表示使能自定义序列号字符串描述符；
             * (14)、1个字节芯片USB键盘快速上传标志(仅ASCII模式有效)，有效范围为0x00--0x01，
             * 0x00 表示USB键盘上传速度正常，
             * 0x01表示使能USB键盘快速上传模式，使能快速上传模式后，上传完1 个字符后不发送释放按键包，继续上传下一个字符，只到所有字符上传完毕再发送释放按键包。
             * (15)、12 个字节保留；
             */
            /// <summary>
            /// 通过该命令向芯片设置当前参数配置信息,具体参数格式见上一条指令描述
            /// </summary>
            CMD_SET_PARA_CFG = 0x09,
            /*帧头 地址码 命令码 后续数据长度 后续数据 累加和
             * HEAD ADDR CMD LEN DATA SUM
             * 0x57、0xAB 0x00 0x09 50 50 个字节数据 0x??
             * 
             * 芯片à外围串口设备：
             * 帧头 地址码 命令码 后续数据长度 后续数据 累加和
             * HEAD ADDR CMD LEN DATA SUM
             * 0x57、0xAB 0x00 0x89 1 1 个字节数据 0x??
             */
            /// <summary>
            /// 通过该命令向芯片获取当前所使用的USB 字符串描述符配置。
            /// </summary>
            CMD_GET_USB_STRING = 0x0A,
            /* 帧头 地址码 命令码 后续数据长度 后续数据 累加和
             * HEAD ADDR CMD LEN DATA SUM
             * 0x57、0xAB 0x00 0x0A 1 1 个字节数据 0x??
             * 
             * 该命令带1 个字节的参数，依次为：
             * (1)、1 个字节字符串类型，
             * 0x00表示厂商字符串描述符；
             * 0x01 表示产品字符串描述符；
             * 0x02 表示序列号字符串描述符；
             * 
             * 芯片à外围串口设备：
             * 帧头 地址码 命令码 后续数据长度 后续数据 累加和
             * HEAD ADDR CMD LEN DATA SUM
             * 0x57、0xAB 0x00 0x8A 2+N 2+N 个字节数据 0x??
             * 
             * 返回的2+N个字节后续数据，依次为：
             * (1)、1个字节字符串类型；
             * (2)、1个字节字符串长度，有效范围为0---23；
             * (3)、N 个字节当前字符串描述符，N 有效范围为：1-23；
             */
            /// <summary>
            /// 通过该命令向芯片设置当前所使用的USB 字符串描述符配置。
            /// </summary>
            CMD_SET_USB_STRING = 0x0B,
            /* 帧头 地址码 命令码 后续数据长度 后续数据 累加和
             * HEAD ADDR CMD LEN DATA SUM
             * 0x57、0xAB 0x00 0x0B 2+N 2+N 个字节数据 0x??
             * 该命令带2+N 个字节的参数，依次为：
             * (1)、1 个字节字符串类型，0x00表示厂商字符串描述符；0x01 表示产品字符串描述符；
             * 0x02 表示序列号字符串描述符；
             * (2)、1个字节字符串长度，有效范围为0---23；
             * (3)、N 个字节字符串描述符，N 有效范围为：1-23；
             * 
             * 芯片à外围串口设备：
             * 帧头 地址码 命令码 后续数据长度 后续数据 累加和
             * HEAD ADDR CMD LEN DATA SUM
             * 0x57、0xAB 0x00 0x8B 1 1 个字节数据 0x??
             * 返回的1 个字节后续数据为：当前命令执行状态。
             */
            /// <summary>
            /// 通过该命令将芯片的参数配置及字符串配置信息恢复到出厂默认设置。
            /// </summary>
            CMD_SET_DEFAULT_CFG = 0x0C,
            /* 帧头 地址码 命令码 后续数据长度 后续数据 累加和
             * HEAD ADDR CMD LEN DATA SUM
             * 0x57、0xAB 0x00 0x0C 0 无 0x??
             * 该命令不带任何参数。
             * 
             * 芯片à外围串口设备：
             * 帧头 地址码 命令码 后续数据长度 后续数据 累加和
             * HEAD ADDR CMD LEN DATA SUM
             * 0x57、0xAB 0x00 0x8C 1 1 个字节数据 0x??
             * 返回的1 个字节后续数据为：当前命令执行状态。
             */
            /// <summary>
            /// 通过该命令控制芯片进行软件复位控制。
            /// </summary>
            CMD_RESET = 0x0F
            /* 外围串口设备à芯片：
             * 帧头 地址码 命令码 后续数据长度 后续数据 累加和
             * HEAD ADDR CMD LEN DATA SUM
             * 0x57、0xAB 0x00 0x0F 0 无 0x??
             */
        }

        /* 错误应答包格式：
         * 芯片接收到的命令包如果存在命令码错误、校验错误或者执行失败等问题时，则需要通过错误应答包进行应答。错误应答包包含1 个字节后续数据，该数据即为命令执行状态。
         * 芯片à外围串口设备：
         * 帧头 地址码 命令码 后续数据长度 后续数据 累加和
         * HEAD ADDR CMD LEN DATA SUM
         * 0x57、0xAB 0x00 0xC? 1 1 个字节数据 0x??
         * 返回的1 个字节后续数据为：当前命令执行状态。
         * 返回的命令码 = 原命令码 | 0xC0；
         * 表2-命令执行状态如下表
         * 状态名称 状态码 状态说明
         * DEF_CMD_SUCCESS 0x00 命令执行成功
         * DEF_CMD_ERR_TIMEOUT 0xE1 串口接收一个字节超时
         * DEF_CMD_ERR_HEAD 0xE2 串口接收包头字节出错
         * DEF_CMD_ERR_CMD 0xE3 串口接收命令码错误
         * DEF_CMD_ERR_SUM 0xE4 累加和检验值不匹配
         * DEF_CMD_ERR_PARA 0xE5 参数错误
         * DEF_CMD_ERR_OPERATE 0xE6 帧正常，执行失败
         */

        /// <summary>
        /// 键盘和HID代码映射表
        /// </summary>
        public static readonly Dictionary<ConsoleKey, string[]> KeyToCodeMap = new()
        {
            { ConsoleKey.Oem3, new string[] { "~" , "`" , "07" , "35" } },
            { ConsoleKey.D1, new string[] { "!" , "1" , "07" , "1e" } },
            { ConsoleKey.D2, new string[] { "@" , "2" , "07" , "1F" } },
            { ConsoleKey.D3, new string[] { "#" , "3" , "07" , "20" } },
            { ConsoleKey.D4, new string[] { "$" , "4" , "07" , "21" } },
            { ConsoleKey.D5, new string[] { "%" , "5" , "07" , "22" } },
            { ConsoleKey.D6, new string[] { "^" , "6" , "07" , "23" } },
            { ConsoleKey.D7, new string[] { "&" , "7" , "07" , "24" } },
            { ConsoleKey.D8, new string[] { "*" , "8" , "07" , "25" } },
            { ConsoleKey.D9, new string[] { "(" , "9" , "07" , "26" } },
            { ConsoleKey.D0, new string[] { ")" , "0" , "07" , "27" } },
            { ConsoleKey.OemMinus, new string[] { "_" , "-" , "07" , "2D" } },
            { ConsoleKey.OemPlus, new string[] { "+" , "=" , "07" , "2E" } },
            //Keycode14 (*J) 07 89
            { ConsoleKey.Backspace, new string[] { "\b" , "\b" , "07" , "2A" } },
            { ConsoleKey.Tab, new string[] { "\t" , "\t" , "07" , "2B" } },
            { ConsoleKey.Q, new string[] { "Q" , "q" , "07" , "14" } },
            { ConsoleKey.W, new string[] { "W" , "w" , "07" , "1A" } },
            { ConsoleKey.E, new string[] { "E" , "e" , "07" , "08" } },
            { ConsoleKey.R, new string[] { "R" , "r" , "07" , "15" } },
            { ConsoleKey.T, new string[] { "T" , "t" , "07" , "17" } },
            { ConsoleKey.Y, new string[] { "Y" , "y" , "07" , "1C" } },
            { ConsoleKey.U, new string[] { "U" , "u" , "07" , "18" } },
            { ConsoleKey.I, new string[] { "I" , "i" , "07" , "0C" } },
            { ConsoleKey.O, new string[] { "O" , "o" , "07" , "12" } },
            { ConsoleKey.P, new string[] { "P" , "p" , "07" , "13" } },
            { ConsoleKey.Oem4, new string[] { "{" , "[" , "07" , "2F" } },
            { ConsoleKey.Oem6, new string[] { "}" , "]" , "07" , "30" } },
            //Caps Lock 07 39
            { ConsoleKey.A, new string[] { "A" , "a" , "07" , "04" } },
            { ConsoleKey.S, new string[] { "S" , "s" , "07" , "16" } },
            { ConsoleKey.D, new string[] { "D" , "d" , "07" , "07" } },
            { ConsoleKey.F, new string[] { "F" , "f" , "07" , "09" } },
            { ConsoleKey.G, new string[] { "G" , "g" , "07" , "0A" } },
            { ConsoleKey.H, new string[] { "H" , "h" , "07" , "0B" } },
            { ConsoleKey.OemPeriod, new string[] { ">" , "." , "07" , "37" } },
            { ConsoleKey.Oem2, new string[] { "?" , "/" , "07" , "38" } },
            //Keycode56 (*BJ) 07 87
            //Shift (R) 07 E5
            //Ctrl (L) 07 E0
            //Alt (L) 07 E2
            { ConsoleKey.Spacebar, new string[] { " " , " " , "07" , "2C" } },
            //Alt (R) 07 E6
            //Ctrl (R) 07 E4
            { ConsoleKey.Insert, new string[] { "" , "" , "07" , "49" } },
            { ConsoleKey.Delete, new string[] { "" , "" , "07" , "4C" } },
            { ConsoleKey.LeftArrow, new string[] { "" , "" , "07" , "50" } },
            { ConsoleKey.RightArrow, new string[] { "" , "" , "07" , "4F" } },
            { ConsoleKey.UpArrow, new string[] { "" , "" , "07" , "52" } },
            { ConsoleKey.DownArrow, new string[] { "" , "" , "07" , "51" } },
            { ConsoleKey.Home, new string[] { "" , "" , "07" , "4A" } },
            { ConsoleKey.End, new string[] { "" , "" , "07" , "4D" } },
            //↑ 07 52
            //↓ 07 51
            { ConsoleKey.PageUp, new string[] { "" , "" , "07" , "4B" } },
            { ConsoleKey.PageDown, new string[] { "" , "" , "07" , "4E" } },
            //→ 07 4F
            //Num Lock 07 53
            //7 Home 07 5F
            //4 ← 07 5C
            //1 End 07 59
            // / 07 54
            //8 ↑ 07 60
            //5 07 5D
            //2 ↓ 07 5A
            //0 Ins 07 62
            //* 07 55
            //9 PgUp 07 61
            //6 → 07 5E
            //3 PgDn 07 5B
            //. Del 07 63
            //- 07 56
            //+ 07 57
            //Keycode107 (*B) 07 85
            { ConsoleKey.J, new string[] { "J" , "j" , "07" , "0D" } },
            { ConsoleKey.K, new string[] { "K" , "k" , "07" , "0E" } },
            { ConsoleKey.L, new string[] { "L" , "l" , "07" , "0F" } },
            { ConsoleKey.Oem1, new string[] { ":" , ";" , "07" , "33" } },
            { ConsoleKey.Oem7, new string[] { "\"" , "'" , "07" , "34" } },
            //Keycode42 (*5BJ) 07 32
            { ConsoleKey.Enter, new string[] { "\r" , "\r" , "07" , "28" } },
            //Shift (L) 07 E1
            //Keycode45 (*5B) 07 64
            { ConsoleKey.Z, new string[] { "Z" , "z" , "07" , "1D" } },
            { ConsoleKey.X, new string[] { "X" , "x" , "07" , "1B" } },
            { ConsoleKey.C, new string[] { "C" , "c" , "07" , "06" } },
            { ConsoleKey.V, new string[] { "V" , "v" , "07" , "19" } },
            { ConsoleKey.B, new string[] { "B" , "b" , "07" , "05" } },
            { ConsoleKey.N, new string[] { "N" , "n" , "07" , "11" } },
            { ConsoleKey.M, new string[] { "M" , "m" , "07" , "10" } },
            { ConsoleKey.OemComma, new string[] { "<" , "," , "07" , "36" } },
            //Enter_R 07 58
            { ConsoleKey.Escape, new string[] { "" , "" , "07" , "29" } },
            { ConsoleKey.F1, new string[] { "" , "" , "07" , "3A" } },
            { ConsoleKey.F2, new string[] { "" , "" , "07" , "3B" } },
            { ConsoleKey.F3, new string[] { "" , "" , "07" , "3C" } },
            { ConsoleKey.F4, new string[] { "" , "" , "07" , "3D" } },
            { ConsoleKey.F5, new string[] { "" , "" , "07" , "3E" } },
            { ConsoleKey.F6, new string[] { "" , "" , "07" , "3F" } },
            { ConsoleKey.F7, new string[] { "" , "" , "07" , "40" } },
            { ConsoleKey.F8, new string[] { "" , "" , "07" , "41" } },
            { ConsoleKey.F9, new string[] { "" , "" , "07" , "42" } },
            { ConsoleKey.F10, new string[] { "" , "" , "07" , "43" } },
            { ConsoleKey.F11, new string[] { "" , "" , "07" , "44" } },
            { ConsoleKey.F12, new string[] { "" , "" , "07" , "45" } },
            { ConsoleKey.PrintScreen, new string[] { "" , "" , "07" , "46" } },
            //Scroll Lock 07 47
            { ConsoleKey.Pause, new string[] { "" , "" , "07" , "48" } },
            //* 4 _ 104 Keyboard Only
            //* 5 _ 105 Keyboard Only
            //*B _ 107 Keyboard Only
            //*J _ 109 Keyboard Only
            //131 (*J) Japanese J131 07 8B
            //132 (*J) Japanese J132 07 8A
            //133 (*J) Japanese J133 07 88
            //150 KoreaKC-L,Key_Hangul 07 90
            //151 Korea KC-R, Key_Hanja 07 91
            //ACPI Power 01 81
            //ACPI Sleep 01 82
            //ACPI Wake-up 01 83
            
            //对于ACPI 键，共2个字节，第1 个字节为REPORT ID，固定为0x01，第2 个字节为ACPI键码。
            //字节号 Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
            //1 00000001b
            //2 00000b Wake-up Sleep Power
            //1:键按下 0:键释放

            { ConsoleKey.LeftWindows, new string[] { "" , "" , "07" , "E3" } },
            { ConsoleKey.RightWindows, new string[] { "" , "" , "07" , "E7" } },
            { ConsoleKey.Applications, new string[] { "" , "" , "07" , "65" } },
            { ConsoleKey.MediaStop, new string[] { "" , "" , "07" , "65" } },
        };

        /// <summary>
        /// 通信以帧为单位，即以数据包的形式发送
        /// </summary>
        public class FramePackage
        {
            /// <summary>
            /// 外围串口设备发起的通信帧称为“命令包”，CH9329 芯片返回的通信帧称为“应答包”。
            /// </summary>
            public bool SentOrRecieved = true;
            ///每帧数据都带有帧头字节、地址码、命令码、后续数据长度、后续数据以及累加和(作为校验)
            ///如果CH9329 芯片接收到错误帧，则返回错误应答帧或者将其直接丢弃。
            public bool Error = false;

            /// <summary>
            /// 帧头
            /// </summary>
            public readonly byte[] FrameHEAD = [0x57, 0xAB];
            /// <summary>
            /// 地址码
            /// 默认为0x00，可接收任意地址码的命令包，如果芯片地址设置成0x01---0xFE，则只能接收对应地址码或地址码为0xFF 的命令包。
            /// 0xFF为广播包，芯片不需要进行应答；
            /// </summary>
            public byte FrameADDR = 0x00;
            /// <summary>
            /// 命令码
            /// 占1个字节，外围串口设备发起的帧的命令码有效范围为：0x01---0x3F
            /// CH9329芯片发送正常应答包时的命令码为：原命令码 | 0x80
            /// CH9329芯片发送异常应答包时的命令码为：原命令码 | 0xC0
            /// </summary>
            public byte FrameCMD = 0x01;
            /// <summary>
            /// 后续数据长度，最大64
            /// </summary>
            public byte FrameLEN = 0x00;
            /// <summary>
            /// 后续数据
            /// </summary>
            public List<byte> FrameDATA = [];
            /// <summary>
            /// 累加和
            /// </summary>
            public byte FrameSUM
            {
                get
                {
                    return Utility.AddCheck([.. FrameHEAD, FrameADDR, FrameCMD, FrameLEN, ..FrameDATA]);
                }
            }
            /// <summary>
            /// 包括校验位的完整数据包
            /// </summary>
            public byte[] FullPackage
            {
                get
                {
                    return [.. FrameHEAD, FrameADDR, FrameCMD, FrameLEN, .. FrameDATA, FrameSUM];
                }
            }
        }

        #region 芯片的工作模式和串口通信模式示例（未使用）
        /// <summary>
        /// CH9329芯片
        /// 封装方式SOP16
        /// 塑体宽度3.9mm、150mil
        /// 引脚间距1.27mm、50mil
        /// 标准16脚贴片
        /// </summary>
        public class CH9329
        {
            /// <summary>
            /// 切换通讯模式
            /// </summary>
            /// <param name="CT">目标模式</param>
            public void ChangeCommunicationType(CommunicationType CT)
            {
                switch (CT)
                {
                    case CommunicationType.Protocal:
                        Setpin("SET", false);
                        Setpin("MODE1", true);
                        Setpin("MODE0", true);
                        break;
                    case CommunicationType.ASCII:
                        Setpin("CFG1", true);
                        Setpin("CFG0", false);
                        break;
                    case CommunicationType.Passthrough:
                        Setpin("CFG1", false);
                        Setpin("CFG0", true);
                        break;
                    default:
                        return;
                }
            }

            /// <summary>
            /// CH9329芯片内置了电源上电复位电路，复位完成后，芯片根据当前配置的工作模式和串口通讯模式进行工作。
            /// </summary>
            public void Reset()
            {

                for (int i = 0; i < Pins.Length; i++)
                {
                    Pins[i] = false;
                }
                Thread.Sleep(3100);
                Setpin("DEF", true);
                Thread.Sleep(200);
            }


            /// <summary>
            /// H9329芯片有3串口通信种模式：
            /// 如果CFG0引脚和CFG1引脚悬空，则芯片默认处于串口通信模式0。
            /// </summary>
            public enum CommunicationType
            {
                /// <summary>
                /// 模式0：协议传输模式(默认)；
                /// 该模式下客户串口设备向CH9329芯片发送串口数据时，必须按照协议格式发送，否则会直接丢弃。具体协议格式见“CH9329芯片串口通信协议_Vx.x.PDF”。
                /// 该模式一般适用于既需要使用USB键盘功能，又需要使用USB鼠标功能的应用。如果需要使用全键盘功能，也建议采用该模式。
                /// </summary>
                Protocal = 0,
                /// <summary>
                /// 模式1：ASCII 模式；
                /// 该模式下客户串口设备向CH9329芯片发送串口数据时，可以发送ASCII码字符数据，也可以发送区位码汉字数据。
                /// 该模式适用于只需要使用USB键盘中可见ASCII字符的应用。
                /// </summary>
                ASCII = 1,
                /// <summary>
                /// 模式2：透传模式。
                /// 该模式下客户串口设备向CH9329芯片发送串口数据时，可以是任意16进制数据。
                /// 该模式适用于CH9329芯片处于芯片工作模式3的应用。
                /// </summary>
                Passthrough = 2,
            }

            /// <summary>
            /// 工作模式
            /// 如果MODE0引脚和MODE1引脚悬空，则芯片默认处于工作模式0。
            /// </summary>
            public enum WorkMode
            {
                /// <summary>
                /// MODE1电平1 MODE0电平1 模拟标准USB键盘+USB鼠标设备+USB自定义HID类设备(默认)
                /// 该模式下CH9329芯片在电脑上识别为USB键盘、USB鼠标和自定义HID类设备的多功能复合设备，USB键盘包含普通键和多媒体键，USB鼠标包含相对鼠标和绝对鼠标。
                /// 该模式功能最全，可以实现USB键盘和USB鼠标的全部功能。
                /// </summary>
                KeyMouseAndCustom = 0,
                /// <summary>
                /// MODE1电平1 MODE0电平0 模拟标准USB键盘设备
                /// 该模式下CH9329芯片在电脑上识别为单一USB键盘设备，USB键盘只包含普通键，不包含多媒体键，支持全键盘模式，适用于部分不支持复合设备的系统。
                /// </summary>
                Key = 1,
                /// <summary>
                /// MODE1电平0 MODE0电平1 模拟标准USB键盘+USB鼠标设备
                /// 该模式下CH9329芯片在电脑上识别为USB键盘和USB鼠标的多功能复合设备，USB键盘包含普通键和多媒体键，USB鼠标包含相对鼠标和绝对鼠标。
                ///注：Linux/Android/苹果等操作系统下，建议使用该模式。
                /// </summary>
                KeyAndMouse = 2,
                /// <summary>
                /// MODE1电平0 MODE0电平0 模拟标准USB自定义HID类设备
                /// 该模式下CH9329芯片在电脑上识别为单一USB自定义HID类设备，具有上传和下传2个通道，可以实现串口和HID数据透传功能。
                /// CH9329芯片如果接收到串口数据，则打包通过USB上传，如果接收到USB下传数据，则通过串口进行发送。
                /// </summary>
                Custom = 3,
            }

            //readonly CommunicationType CurrentCT = CommunicationType.Protocal;
            //readonly WorkMode CurrentWM = WorkMode.KeyMouseAndCustom;

            ///CH9329芯片内置了串口转USB HID类通讯的相关固件，是串口转HID类设备简单快捷的解决方案。
            ///此外它还内置了USB总线所需的所有外围电路，包括PLL和24MHz的USB时钟、D+和D-信号线的串联匹配电阻、Device设备的1.5KΩ上拉电阻等，并且内置了晶振，外围电路非常简单。
            ///CH9329芯片符合相关技术规范，支持即插即用，计算机端的Windows/Linux/Android/MAC等操作系统已经内置相应的驱动程序，连接后即可使用。

            ///CH9329 芯片默认工作在串口通信模式0(协议传输模式),本协议主要用于指定CH9329 芯片工作在该模式下的串口通信协议。
            ///任何模式下，芯片检测到该SET引脚为低电平后自动切换到“协议传输模式”，客户端串口设备可进行参数配置。
            ///因此，需要进行参数配置时，可以先设置SET 引脚为低电平，然后再进行配置。

            /// <summary>
            /// 引脚状态
            /// </summary>
            public bool[] Pins = new bool[16];

            /// <summary>
            /// CH9329内置了独立的收发缓冲区，支持单工、半双工或者全双工异步串行通讯
            /// 串行数据包括1个低电平起始位，8个数据位，1个高电平停止位
            /// </summary>
            public byte[] Buffer = new byte[16];

            /// <summary>
            /// 默认波特率
            /// </summary>
            public static readonly int DefaultBaudrate = 9600;
            /// <summary>
            /// 工作波特率
            /// </summary>
            public int CurrentBaudrate = DefaultBaudrate;
            /// <summary>
            /// 波特率。芯片在3.3V电源电压下工作时不支持115200。
            /// 串口发送信号的波特率误差小于0.3％
            /// 串口接收信号的允许波特率误差不小于2％。
            /// </summary>
            public int[] Baudrates = [1200, 2400, 4800, 9600, 14400, 19200, 38400, 57600, 115200];

            /// <summary>
            /// 根据引脚名称修改引脚电平
            /// </summary>
            /// <param name="pinname">引脚名称</param>
            /// <param name="pinlevel">引脚电平</param>
            /// <returns></returns>
            public bool Setpin(string pinname, bool pinlevel)
            {
                switch (pinname)
                {
                    case "ACT#": //01 输出 USB配置完成状态输出引脚，低电平有效。CH9329芯片的ACT#引脚是USB设备配置完成状态输出，用于指示USB设备已经成功连接到计算机。
                        Pins[0] = pinlevel;
                        return true;
                    case "MODE0": //02 输入 芯片工作模式配置引脚0，配合MODE1引脚使用，内置上拉电阻
                        Pins[1] = pinlevel;
                        return true;
                    case "MODE1": //03 输入 芯片工作模式配置引脚1，配合MODE0引脚使用，内置上拉电阻
                        Pins[2] = pinlevel;
                        return true;
                    case "CFG0": //04 输入 芯片串口通信模式配置引脚0，配合CFG1引脚使用，内置上拉电阻
                        Pins[3] = pinlevel;
                        return true;
                    case "CFG1": //05 输入 芯片串口通信模式配置引脚1，配合CFG0引脚使用，内置上拉电阻
                        Pins[4] = pinlevel;
                        return true;
                    case "RST": //06 输入 外部复位输入引脚，高电平有效，内置下拉电阻
                        Pins[5] = pinlevel;
                        return true;
                    case "TXT": //07 输出 串行数据输出
                        Pins[6] = pinlevel;
                        return true;
                    case "RXD": //08 输入 串行数据输入，内置上拉电阻
                        Pins[7] = pinlevel;
                        return true;
                    case "UP": //09 输出 USB数据包上传成功指示引脚，每成功上传一包数据，该引脚电平翻转一次；注：如果芯片需要在3.3V下工作，必须在该引脚对地加1个4.7K下拉电阻；
                        Pins[8] = pinlevel;
                        return true;
                    case "DEF": //10 输入 芯片参数恢复出厂设置引脚，拉低3S以上可将参数恢复出厂默认设置，内置上拉电阻
                        Pins[9] = pinlevel;
                        return true;
                    case "SET": //11 输入 芯片参数配置引脚，低电平有效，内置上拉电阻。任何模式下，芯片检测到该引脚为低电平后自动切换到“协议传输模式”，客户端串口设备可进行参数配置
                        Pins[10] = pinlevel;
                        return true;
                    case "UD+": //12 USB信号 直接连到USB总线的D+数据线
                        Pins[11] = pinlevel;
                        return true;
                    case "UD-": //13 USB信号 直接连到USB总线的D-数据线
                        Pins[12] = pinlevel;
                        return true;
                    case "GND": //14 电源 公共接地端，直接连到USB总线的地线
                        Pins[13] = pinlevel;
                        return true;
                    case "VCC": //15 电源 正电源输入端，需要外接0.1uF电源退耦电容
                        Pins[14] = pinlevel;
                        return true;
                    case "V33": //16 电源 内部USB电源调整器输出和内部USB电源输入，CH9329芯片使用5V电源电压时，V33引脚应该外接容量为0.1uF左右的电源退耦电容。使用3.3V电源电压时，V33引脚应该与VCC引脚相连接，同时输入外部的3.3V电源。
                        Pins[15] = pinlevel;
                        return true;
                    default:
                        return false;
                }
            }
        }

        /// <summary>
        /// 外围串口设备为主机
        /// </summary>
        public class ServerDevice
        {
            readonly string Command = string.Empty;
        }

        /// <summary>
        /// 芯片所在端连接从机
        /// </summary>
        public class ClientDevice(CH9329 chip, string name)
        {
            private readonly string DeviceName = name;
            private readonly CH9329 Chip = chip;
            /// <summary>
            /// 外围串口设备在500mS内接收不到CH9329 芯片的应答或者应答信息错误，则认为本次通信失败
            /// </summary>
            readonly TimeSpan ResponceTime = TimeSpan.FromMicroseconds(500);
            readonly string Response = string.Empty;

            /// <summary>
            /// 对返回包的CMD位进行计算，进行ADDCheck，前提是fp已经合理设置了SentOrRecieved = true，Error根据实际情况设置
            /// </summary>
            /// <param name="fp">数据包</param>
            public static FramePackage ChangeFPCMD(FramePackage fp)
            {
                if (fp is not null && fp.SentOrRecieved && !fp.Error)
                {
                    fp.FrameCMD |= 0x80;
                    return fp;
                }
                else if (fp is not null && fp.SentOrRecieved && fp.Error)
                {
                    fp.FrameCMD |= 0xC0;
                    return fp;
                }
                else
                {
                    return fp is not null ? fp : new FramePackage();
                }
            }
        }
        #endregion
    }
}